package pl.edu.mimuw.irs.core.model.analysis.workflow;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import pl.edu.mimuw.irs.core.model.analysis.History;
import pl.edu.mimuw.irs.core.model.analysis.TPair;
import pl.edu.mimuw.irs.core.model.analysis.atomic.AnalysisNodeFactory;
import pl.edu.mimuw.irs.core.model.analysis.dd.ActivityWrapper;
import pl.edu.mimuw.irs.core.model.analysis.resources.Fire;
import pl.edu.mimuw.irs.core.model.analysis.resources.Hire;
import pl.edu.mimuw.irs.core.model.analysis.resources.ResourcesGraph;
import pl.edu.mimuw.irs.core.model.analysis.util.TransitionsRandomizer;
import pl.edu.mimuw.irs.core.model.design.atomic.AtomicSubnet;
import pl.edu.mimuw.irs.core.model.design.net.IRSActivity;
import pl.edu.mimuw.irs.core.model.design.net.IRSRequirement;
import pl.edu.mimuw.irs.core.model.design.net.IRSWorkflow;
import pl.edu.mimuw.irs.core.model.design.resource.Resources;
import pl.edu.mimuw.irs.core.model.petrinet.Arc;
import pl.edu.mimuw.irs.core.model.petrinet.Place;
import pl.edu.mimuw.irs.core.model.petrinet.Transition;

/**
 * @author Jakub Rauch
 * Created on: 2009-05
 */
public class AnalysisWorkflow extends ResourcesWorkflow {
 
	private static final long serialVersionUID = 5752913375201843857L;
	
	// *** Attributes

	private ResourcesGraph graph;
	private Map<IRSActivity, TPair> activityHiringMap;
	private AtomicSubnet atomicNet;
//	private Map<IRSActivity, Set<ResourcePlace>> hiredResources;
	public ResourcesGraph getGraph() {
		return graph;
	}
	public Map<IRSActivity, TPair> getActivityHiringMap() {
		if (activityHiringMap == null) activityHiringMap = 
			new LinkedHashMap<IRSActivity, TPair>();
		return activityHiringMap;
	}
	public AtomicSubnet getAtomicNet() {
		return atomicNet;
	}
//	public Map<IRSActivity, Set<ResourcePlace>> getHiredResources() {
//		if (hiredResources == null) hiredResources = 
//			new LinkedHashMap<IRSActivity, Set<ResourcePlace>>();
//		return hiredResources;
//	}

	
	// *** Constructors

	public AnalysisWorkflow(IRSWorkflow workflow, Resources resources) {
		super(workflow, resources);
		
		atomicNet = getWorkflow().getRootPlace().getAtomicWorkflow(
				new AnalysisNodeFactory(getIdManager()));
		moveAtomicNet();

		this.graph = new ResourcesGraph(workflow, 
				getRequirementsMap(),
				getResourcesMap());
		
		linkActivitiesToResources();
		loadNet(atomicNet);
		linkInOut();
		
		reset();
	}
	
	// *** Methods

	public void reset() {
		getOut().setTokens(0);
		getIn().setTokens(1);
		getGraph().reset();
	}
	
	public History run() {
		/* Execute the net randomly */
		History history = new History();
		getInitialize().invoke();
		
		List<Transition> transitions = getAtomicNet().getActiveTransitions();
		TransitionsRandomizer randomizer = new TransitionsRandomizer(transitions);
		
		while (!getFinalize().isActive()) {
			Transition transition = randomizer.getRandom();
			
			/*if (randomizer.map.size() > 2) {
				Transition[] table = new Transition[3];
				int i = 0;
				for (Transition t : randomizer.map.values()) {
					table[i++] = t;
				}
				table.toString();
			}*/
			if (transition.invoke()) {
				history.addInvoke(transition);
				transitions = getAtomicNet().getActiveTransitions();
				if (transitions.size() == 0 && !getFinalize().isActive()) {
					/*
					 * If there are no other ways but workflow is not ended - cancel run.
					 */
					cancel();
					return null;
				}
			} else {
				if (transitions.size() == 1) {
					/*
					 * If this was last transition that could be invoked - cancel run.
					 */
					cancel();
					return null;
				}
				/*
				 * If transition invoke failed it means it cannot be invoked now, so remove it and 
				 * rerun.
				 */
				transitions.remove(transition);
			}
			randomizer = new TransitionsRandomizer(transitions);
		}
		
		getFinalize().invoke();
		
		/*
		 * One run history built.
		 */
		
		return history;
	}
	
	private void cancel() {
		for (ActivityWrapper activity : getGraph().getActivities().values()) {
			if (activity.getInvolvedResources().size() > 0) {
				// XXX : a little bit dirty.
				Fire fire = (Fire) getActivityHiringMap().get(activity.getActivity()).getFire();
				fire.quietInvoke();
			}
			activity.reset();
		}
		for (Place place : getAtomicNet().getPlaces()) {
			place.setTokens(0);
		}
	}
	
	private void linkActivitiesToResources() {
		Map<IRSActivity, Set<Transition>> activitiesStarts = new LinkedHashMap<IRSActivity, Set<Transition>>();
		Map<IRSActivity, Set<Transition>> activitiesEnds = new LinkedHashMap<IRSActivity, Set<Transition>>();
		
		for (IRSActivity activity : getWorkflow().getActivitiesDFS()) {
			/*
			 * Very important! We assume that activities returned by this
			 * getActivities() are in DFS order.
			 * Requirement describes where the activity starts and ends
			 * in the atomic workflow. Using its arcs we can create additional
			 * Hire and Fire elements in the beginning and at the end of the
			 * activity.
			 */
			IRSRequirement irsRequirement = activity.getRequirement();
			Place requirement = getAtomicNet().getPlacesMap().get(irsRequirement);
			Set<Arc> startArcs = new LinkedHashSet<Arc>(requirement.getOutgoingArcs());
			Set<Arc> endArcs = new LinkedHashSet<Arc>(requirement.getIncomingArcs());
			Set<Transition> startTransitions = new LinkedHashSet<Transition>();
			Set<Transition> endTransitions = new LinkedHashSet<Transition>();
			for (Arc arc : startArcs) startTransitions.add(arc.getTransition());
			for (Arc arc : endArcs) endTransitions.add(arc.getTransition());
			/*
			 * These arcs and requirement place will not be required any more - 
			 * we can remove them.
			 */
			for (Arc arc : startArcs) arc.destroy();
			for (Arc arc : endArcs) arc.destroy();
			getAtomicNet().removeNode(requirement);
			/* 
			 * Now, knowing which transitions are the starting/ending transitions
			 * we can save them in map and so later all will be replaced with 
			 * proper hiring elements.
			 */
			activitiesStarts.put(activity, startTransitions);
			activitiesEnds.put(activity, endTransitions);
		}
		
		/*
		 * Now no more resource places from the old net is present, so we can
		 * begin replacing start/end arcs with hiring elements.
		 */
		for (IRSActivity activity : getWorkflow().getActivitiesDFS()) {
			createHiring(activity, 
					activitiesStarts.get(activity),
					activitiesEnds.get(activity));
		}
	}
	
	private void createHiring(IRSActivity activity, 
			Set<Transition> startTransitions, 
			Set<Transition> endTransitions) {
		AtomicSubnet atomicNet = getAtomicNet();
		
		/*
		 * Create new elements.
		 */
		Hire hire = new Hire(getIdManager(), getGraph().getActivities().get(activity), 
				getGraph(), getResourceHiringMap());
		Fire fire = new Fire(getIdManager(), getGraph().getActivities().get(activity), 
				getGraph(), getResourceHiringMap());
		Place afterHire = new Place(getIdManager(), hire.getPosition().right());
		Place beforeFire = new Place(getIdManager(), fire.getPosition().left());
		getActivityHiringMap().put(activity, new TPair(hire, fire));
		
		/*
		 * Add them to the net.
		 */
		atomicNet.addTransition(hire);
		atomicNet.addPlace(afterHire);
		atomicNet.addPlace(beforeFire);
		atomicNet.addTransition(fire);

		atomicNet.addArc(hire, afterHire);
		atomicNet.addArc(beforeFire, fire);
		
		/*
		 * Every transition in this set has entries from the same set of places.
		 * We can take first to get them all.
		 */
		for (Transition transition : startTransitions) {
			Set<Arc> startArcs = transition.getIncomingArcs();
			/*
			 * Each place we will attach to our Hiring transition.
			 */
			for (Arc arc : startArcs) {
				Place place = arc.getPlace();
				atomicNet.addArc(place, hire);
			}
			hire.setPosition(transition.getPosition().left(2));
			afterHire.setPosition(transition.getPosition().left());
			break;
		}
		
		/*
		 * Now all incoming arcs of these transitions can be removed. Incoming
		 * places will get here through the Hiring transition and place. 
		 * We have to attach "afterHire" place to all transitions as well.
		 */
		for (Transition transition : startTransitions) {
			Set<Arc> startArcs = transition.getIncomingArcs();
			for (Object arc : startArcs.toArray()) {
				((Arc)arc).destroy();
			}
			atomicNet.addArc(afterHire, transition);
		}
		
		/*
		 * Similarly the firing. 
		 */
		for (Transition transition : endTransitions) {
			Set<Arc> endArcs = transition.getOutgoingArcs();
			for (Arc arc : endArcs) {
				Place place = arc.getPlace();
				atomicNet.addArc(fire, place);
			}
			beforeFire.setPosition(transition.getPosition().right());
			fire.setPosition(transition.getPosition().right(2));
			break;
		}
		for (Transition transition : endTransitions) {
			Set<Arc> endArcs = transition.getOutgoingArcs();
			for (Object arc : endArcs.toArray()) {
				((Arc)arc).destroy();
			}
			atomicNet.addArc(transition, beforeFire);
		}
	}
	
	private void linkInOut() {
		addArc(getInitialize(), getAtomicNet().getPlacesMap().get(getWorkflow().getIn()));
		addArc(getAtomicNet().getPlacesMap().get(getWorkflow().getOut()), getFinalize());
	}
	
	private void moveAtomicNet() {
		for (Place place : getAtomicNet().getPlaces()) {
			place.setPosition(place.getPosition().right(6));
		}
		for (Transition transition : getAtomicNet().getTransitions()) {
			transition.setPosition(transition.getPosition().right(6));
		}
	}
}
